package com.chinazyjr.framework.web.accessControl;

import java.io.File;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

import javax.annotation.PostConstruct;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import com.chinazyjr.framework.common.config.HConfig;
import com.chinazyjr.framework.common.config.PropertiesUtils;

@Component
public class RequestRateLimitManager {
	
	private static final Logger LOGGER = LoggerFactory.getLogger("LOGGER.WEB_API_REQUEST");
	// 频率控制开关
	public static final String REQUEST_RATE_SWITCH = "request_rate.switch";
	// 频率控制策略
	public static final String REQUEST_RATE_STRATEGY = "request_rate.strategy";
	// 处理线程数
	public static final String REQUEST_RATE_HANDLER_THREADS="request_rate.%s.handler_threads"; 
	// 同步redis周期 (毫秒)
	public static final String REQUEST_RATE_HANLER_RATE = "request_rate.%s.handler_rate";
	// 频率控制周期单位(second,minute,hour,day)
	public static final String  REQUEST_RATE_PERIOD = "request_rate.%s.period";
	// 频率控制周期长度(second,minute,hour,day)
	public static final String REQUEST_RATE_LENGTH = "request_rate.%s.length";
	// 周期内最大请求数
	public static final String REQUEST_RATE_LIMIT = "request_rate.%s.limit";
	// 策略状态
	public static final String REQUEST_RATE_STATUS = "request_rate.%s.status";
	// hash结构,存放请求次数
	public static final String REQUEST_RATE_REDIS_REQUEST_TIMES="request_rate_request_times~";
	// hash结构,存放黑名单
	public static final String REQUEST_RATE_REDIS_BLACK_LIST = "request_rate_black_list~"; 
	// 待处理列表 key:strategy	value:queue
	private static ConcurrentHashMap<String, ConcurrentLinkedQueue<String> > activeStrategyQueue = new ConcurrentHashMap<String, ConcurrentLinkedQueue<String> >();
	// 处理器
	private static ConcurrentHashMap<String,RequestRateLimitHandler> handlers = new ConcurrentHashMap<>();
	// 检查周期
	private static final Long HEALTH_CHECK_PERIOD = TimeUnit.SECONDS.toSeconds(60L);
	// 频率控制开关
	public static AtomicBoolean ratSwtich = new AtomicBoolean(false);
	
	private static ReentrantLock lock = new ReentrantLock();
	
	public static PropertiesConfiguration propsConfig = null;
	
	@Bean(name="propertiesConfiguration")
	public PropertiesConfiguration getPropertiesConfiguration() {
	    PropertiesConfiguration propsConfig = null;
		try {
			propsConfig = new PropertiesConfiguration();
			propsConfig.load("config/context.properties");
			propsConfig.setEncoding("UTF-8");
			propsConfig.setAutoSave(true);
			FileChangedReloadingStrategy reloadingStrategy = new FileChangedReloadingStrategy();
			reloadingStrategy.setRefreshDelay(1000);
			propsConfig.setReloadingStrategy(reloadingStrategy);
			RequestRateLimitManager.propsConfig = propsConfig;
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}
		
	    return propsConfig;
	}
	
	@Autowired
	@Qualifier("propertiesConfiguration")
	public void onStart(PropertiesConfiguration propertiesConfiguration) {
		if (HConfig.CONTEXT.getPropertyBoolean(REQUEST_RATE_SWITCH) != null && HConfig.CONTEXT.getPropertyBoolean(REQUEST_RATE_SWITCH)) {
			String strategys = HConfig.CONTEXT.getProperty(REQUEST_RATE_STRATEGY);
			if (StringUtils.isNotBlank(strategys)) {
				String[] strategyArr = strategys.split(",");
				for (String strategyName : strategyArr) {
					RequestRateLimitHandler handler = new RequestRateLimitHandler(readConfigurationInit(strategyName));
					if (handler.execute() > 0) {
						ratSwtich.set(true);
						activeStrategyQueue.put(strategyName, new ConcurrentLinkedQueue<>());
						handlers.put(strategyName, handler);
					}
				}
			}
		}
	}

	@PostConstruct
	public void protect() {
		if (HConfig.CONTEXT.getPropertyBoolean(REQUEST_RATE_SWITCH)!=null && HConfig.CONTEXT.getPropertyBoolean(REQUEST_RATE_SWITCH)) {
			startMonitor();
			healthCheck();	
		}	
	} 
	
	public static void addNewStrategy(RequestRateLimitConfig config) {
		try {
			if(lock.tryLock(3000, TimeUnit.SECONDS)) {
				String strategyName = config.getStrategy();
				if (activeStrategyQueue.get(config.getStrategy()) != null || handlers.get(strategyName) !=null) {
					LOGGER.info("request_rate fail add new strategy:{} the stragegy is exist",strategyName);
				}else {
					RequestRateLimitHandler handler = new RequestRateLimitHandler(config);
					handler.execute();
					activeStrategyQueue.putIfAbsent(strategyName, new ConcurrentLinkedQueue<>());
					handlers.putIfAbsent(strategyName, handler);
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			if (lock.isHeldByCurrentThread()) {
				lock.unlock();
			}
		}
	}
	
	public static void shutdown() {
		LOGGER.info("request_rate manager start shutdown");
		activeStrategyQueue.clear();
		ratSwtich.set(false);
		handlers.forEachValue(10, new
			Consumer<RequestRateLimitHandler>() {
				@Override
				public void accept(RequestRateLimitHandler t) {
					t.shutdown();
					t.statusCheck();
				}
			});
		handlers.clear();
	}
	
	public static void start(Properties properties) {
		String strategys = properties.getProperty(REQUEST_RATE_STRATEGY);
		if (StringUtils.isNotBlank(strategys)) {
			String[] strategyArr = strategys.split(",");
			for (String strategyName : strategyArr) {
				RequestRateLimitHandler handler = new RequestRateLimitHandler(readConfigurationDyna(strategyName, properties));
				int number = handler.execute();
				if (number> 0) {
					ratSwtich.set(true);
					activeStrategyQueue.put(strategyName, new ConcurrentLinkedQueue<>());
					handlers.put(strategyName, handler);
				}
			}
		}
	}
	
	
	public static void startMonitor() {
		String file = PropertiesUtils.class.getResource("/config").getFile();
		File directory = new File(file);
		LOGGER.info("request_rate monitor directory:{}",directory.getAbsolutePath());
        long interval = TimeUnit.SECONDS.toMillis(5);
        FileAlterationObserver observer = new FileAlterationObserver(directory,FileFilterUtils.and(
                FileFilterUtils.fileFileFilter(),FileFilterUtils.suffixFileFilter(".properties")));
        observer.addListener(new RequestRateListener());
        FileAlterationMonitor monitor = new FileAlterationMonitor(interval,observer);
        try {
			monitor.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	
	public static Boolean asynPush(String userCode) {
		if (RequestRateLimitManager.ratSwtich.get()) {
			for (Entry<String, ConcurrentLinkedQueue<String>> entry : activeStrategyQueue.entrySet()) {
				ConcurrentLinkedQueue<String> queue = entry.getValue();
				if (queue != null) {
					queue.add(userCode);
					LOGGER.info(String.format("request_rate manager push queue:%s |value:%s", entry.getKey(),userCode));
				}
			}
			return syncCheck(userCode);
		}
		return true;
	}
	
	public static ConcurrentLinkedQueue<String> getQueue(String strategyName) {
		return activeStrategyQueue.get(strategyName);
	}
	
	public static RequestRateLimitConfig readConfigurationInit(String strategyName) {
		RequestRateLimitConfig config = new RequestRateLimitConfig();
		config.setStrategy(strategyName);
		config.setStatus(HConfig.CONTEXT.getPropertyInt(String.format(REQUEST_RATE_STATUS, strategyName)));
		config.setLength(HConfig.CONTEXT.getPropertyInt(String.format(REQUEST_RATE_LENGTH, strategyName)));
		config.setPeriod(HConfig.CONTEXT.getPropertyInt(String.format(REQUEST_RATE_PERIOD, strategyName)));
		config.setThreads(HConfig.CONTEXT.getPropertyInt(String.format(REQUEST_RATE_HANDLER_THREADS, strategyName)));
		config.setLimit(HConfig.CONTEXT.getPropertyInt(String.format(REQUEST_RATE_LIMIT, strategyName)));
		config.setRate(HConfig.CONTEXT.getPropertyInt(String.format(REQUEST_RATE_HANLER_RATE, strategyName)));
		return config;
	}
	
	public static RequestRateLimitConfig readConfigurationDyna(String strategyName,Properties properties) {
		if (propsConfig!=null) {
			RequestRateLimitConfig config = new RequestRateLimitConfig();
			config.setStrategy(strategyName);
			Object statusObj = properties.get(String.format(REQUEST_RATE_STATUS, strategyName));
			if (statusObj !=null) {
				config.setStatus(Integer.parseInt(statusObj.toString()));
			}
			
			Object lengthObj = properties.get(String.format(REQUEST_RATE_LENGTH, strategyName));
			if (lengthObj != null) {
				config.setLength(Integer.parseInt(lengthObj.toString()));
			}
			
			Object periodObj = properties.get(String.format(REQUEST_RATE_PERIOD, strategyName));
			if (periodObj != null) {
				config.setPeriod(Integer.parseInt(periodObj.toString()));
			}
			
			Object threadsObj = properties.get(String.format(REQUEST_RATE_HANDLER_THREADS, strategyName));
			if (threadsObj != null) {
				config.setThreads(Integer.parseInt(threadsObj.toString()));
			}
			
			Object limitObj = properties.get(String.format(REQUEST_RATE_LIMIT, strategyName));
			if (limitObj != null) {
				config.setLimit(Integer.parseInt(limitObj.toString()));
			}
			Object rateObj = properties.get(String.format(REQUEST_RATE_HANLER_RATE, strategyName));
			if (rateObj != null) {
				config.setRate(Integer.parseInt(rateObj.toString()));
			}
			return config;
		}
		LOGGER.info("request_rate manager not found properties configuration");
		return null;
	}
	

	public static Boolean syncCheck(String userCode) {
		Boolean pass = true;
		Set<Entry<String,RequestRateLimitHandler>> entrySet = handlers.entrySet();
		for (Entry<String, RequestRateLimitHandler> entry : entrySet) {
			RequestRateLimitHandler handler = entry.getValue();
			if (handler!= null && !handler.pass(userCode)) {
				pass = false;
			}
		}
		return pass;
	}
	
	public static Future<?> healthCheck() {
		return Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				if (handlers.size() == 0) {
					LOGGER.info("request_rate manager health_check no handlers working ...");
				}
				// handler check
				for (Entry<String,RequestRateLimitHandler> entry: handlers.entrySet()) {
					RequestRateLimitHandler handler = entry.getValue();
					handler.statusCheck();
				}
				// queue check
				for (Entry<String, ConcurrentLinkedQueue<String>> entry : activeStrategyQueue.entrySet()) {
					String strategyName = entry.getKey();
					ConcurrentLinkedQueue<String> queue = entry.getValue();
					RequestRateLimitHandler handler = handlers.get(strategyName);
					Boolean isLive = true;
					if (handler == null || !handler.isLive()) {
						LOGGER.error("request_rate manager health_check strategy:{} handler is dead", strategyName);
						// clear queue
						LOGGER.info("request_rate manager health_check prepare clear queue");
						queue.clear();
						if (handler != null && !handler.isLive()) {
							LOGGER.info("request_rate manager health_check reset handler:{}",strategyName);
							// stop receive
							activeStrategyQueue.remove(strategyName);
							handlers.remove(strategyName);
							// shutdown handler
							handler.shutdown();
							// reset handler
							addNewStrategy(readConfigurationInit(strategyName));
						}
					}
					LOGGER.info("request_rate manager health_check strategy:{} |queue_length:{} |handler_is_live:{}", strategyName,queue.size(),isLive);
				}
				
			}
		}, HEALTH_CHECK_PERIOD, HEALTH_CHECK_PERIOD, TimeUnit.SECONDS);
	}
	
	
	
}
